﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Mini_Project
{
    class algorithms
    {
        RecommenderSystem r;
        public helper h;
        // for each item - a sorted list by the conditionary probability of all the other items
        Dictionary<double, List<pair>> itemCP = new Dictionary<double, List<pair>>();

        public algorithms(RecommenderSystem rec)
        {
            r = rec;
        }

        public void trainSVD(double uid, double iid, double rui, double u, double y, double l)
        {
            int i;
            double puqi = 0;
            List<double> temp_pu = r.pu[uid];
            List<double> temp_qi = r.qi[iid];
            List<double> temp_pu2 = r.pu[uid];
            List<double> temp_qi2 = r.qi[iid];
            for (i = 0; i < temp_pu.Count; i++)
            {
                puqi += (r.pu[(int)uid][i] * r.qi[(int)iid][i]);
            }
            double eui = rui - u - r.bi[iid] - r.bu[uid] - puqi;
            r.bu[uid] = r.bu[uid] + y * (eui - (l * r.bu[uid]));
            r.bi[iid] = r.bi[iid] + y * (eui - (l * r.bi[iid]));

            for (i = 0; i < temp_qi.Count; i++)
            {
                temp_pu[i] = temp_pu[i] * eui;
                temp_qi[i] = temp_qi[i] * l;
                temp_qi[i] = temp_pu[i] - temp_qi[i];
                temp_qi[i] = temp_qi[i] * y;
                r.qi[iid][i] = r.qi[iid][i] + temp_qi[i];
            }

            for (i = 0; i < temp_pu.Count; i++)
            {
                temp_qi2[i] = temp_qi2[i] * eui;
                temp_pu2[i] = temp_pu2[i] * l;
                temp_pu2[i] = temp_qi2[i] - temp_pu2[i];
                temp_pu2[i] = temp_pu2[i] * y;
                r.pu[uid][i] = r.pu[uid][i] + temp_pu2[i];
            }
        }

        public double PredictRAU(string sUID, string sIID, string method)
        {
            double wau = 0, ra = 0, top_r = 0, bottom_r = 0, uid;
            List<pair> userRatings;

            if (r.itemKey.ContainsKey(Convert.ToDouble(sIID)))
            {
                userRatings = r.itemKey[Convert.ToDouble(sIID)];
                uid = Convert.ToDouble(sUID);

                ra = h.getAverageUserRating(sUID);

                foreach (pair user in userRatings)
                {
                    KeyValuePair<int, int> users = new KeyValuePair<int, int>(Convert.ToInt16(uid), Convert.ToInt16(user.getFirst()));
                    if (user.getFirst() != uid)
                    {
                        if (method == "Pearson")
                        {
                            if (r.wau_pearson.ContainsKey(users))
                            {
                                wau = r.wau_pearson[users];
                            }
                            else
                            {
                                wau = calc_wau_pearson(uid, user.getFirst(), ra);
                            }
                        }
                        else
                        {
                            if (r.wau_cosine.ContainsKey(users))
                            {
                                wau = r.wau_cosine[users];
                            }
                            else
                            {
                                wau = calc_wau_cosine(Convert.ToDouble(sUID), user.getFirst());
                            }
                        }
                        if (wau > 0)
                        {
                            top_r += (wau * user.getSecond());
                            bottom_r += wau;
                        }
                    }
                }
                if (bottom_r == 0)
                {
                    return r.u;
                }
                return (top_r / bottom_r);
            }
            else
            {
                return h.getAverageUserRating(sUID);
            }
        }

        public double randomPredict(string sUID)
        {
            double UID = Convert.ToDouble(sUID);
            Random r = new Random();
            Dictionary<double, int> histogram = this.r.GetRatingsHistogram(sUID);
            int totalRatings = this.r.userKey[UID].Count;
            int randRating = r.Next(0, totalRatings);
            int rating = 1;
            while ((randRating >= 0) && (rating < 6))
            {
                if (randRating <= histogram[rating])
                {
                    return rating;
                }
                else
                {
                    randRating = randRating - histogram[rating];
                    rating++;
                }
            }
            return -1;
        }

        public double calc_wau_SVD(double a, double u)
        {
            int i;
            double wau = 0;
            List<double> vector_a = r.pu[a];
            List<double> vector_u = r.pu[u];

            for (i = 0; i < vector_a.Count; i++)
            {
                wau = Math.Sqrt(Math.Pow((vector_a.ElementAt(i) - vector_u.ElementAt(i)), 2));
            }

            return wau;
        }

        public double calc_wau_cosine(double a, double u)
        {
            double rai, rui, top = 0, bottom_a = 0, bottom_b = 0, wau;
            KeyValuePair<int, int> users = new KeyValuePair<int, int>(Convert.ToInt16(a), Convert.ToInt16(u));
            List<pair> items = r.userKey[a];
            foreach (pair item in items)
            {
                rai = item.getSecond();
                rui = r.GetRating(u.ToString(), item.getFirst().ToString());
                top += (rui * rai);
                bottom_a += Math.Pow(rui, 2);
                bottom_b += Math.Pow(rai, 2);
            }
            foreach (pair item in r.userKey[a])
            {
                if (!(items.Contains(item)))
                {
                    rui = item.getSecond();
                    bottom_a += Math.Pow(rui, 2);
                }
            }
            if (bottom_a == 0 || bottom_b == 0)
            {
                wau = 1;
            }
            else
            {
                wau = (top / (Math.Sqrt(bottom_a) * Math.Sqrt(bottom_b)));
            }
            if (r.wau_cosine.ContainsKey(users))
            {
                r.wau_cosine[users] = wau;
            }
            else
            {
                r.wau_cosine.Add(users, wau);
            }
            return wau;
        }

        public double calc_wau_pearson(double a, double u, double ra)
        {
            List<pair> userItems;
            double wau = 0, rui, rai, ru = 0, top = 0, bottom_a = 0, bottom_b = 0;

            KeyValuePair<int, int> users = new KeyValuePair<int, int>(Convert.ToInt16(a), Convert.ToInt16(u));
            userItems = r.userKey[a];
            ru = h.getAverageUserRating(u.ToString());
            foreach (pair item in userItems)
            {
                rui = r.GetRating(u.ToString(), item.getFirst().ToString());
                if (rui > 0)
                {
                    rai = item.getSecond();
                    top += ((rui - ru) * (rai - ra));
                    bottom_a += Math.Pow((rui - ru), 2);
                    bottom_b += Math.Pow((rai - ra), 2);
                }
            }
            if (bottom_a == 0 || bottom_b == 0)
            {
                wau = 1;
            }
            else
            {
                wau = (top / (Math.Sqrt(bottom_a) * Math.Sqrt(bottom_b)));
            }
            if (r.wau_pearson.ContainsKey(users))
            {
                r.wau_pearson[users] = wau;
            }
            else
            {
                r.wau_pearson.Add(users, wau);
            }
            return wau;
        }

        // calculate conditional probability given an item
        public List<pair> CalculateItemCP(double i1)
        {
            if (itemCP.ContainsKey(i1))
            {
                return itemCP[i1];
            }
            else
            {
                // first double - item, second double - CP of item, given i1
                Dictionary<double, double> recs = new Dictionary<double, double>();
                foreach (KeyValuePair<double, List<pair>> item in r.itemKey)
                {
                    if (item.Key != i1)
                        recs.Add(item.Key, 0);
                }

                int i1Count = 0;
                foreach (KeyValuePair<double, List<pair>> u in r.userKey)
                {
                    List<pair> ratings = u.Value;
                    if (h.ContainsFirst(ratings, i1))
                    {
                        i1Count++;
                        foreach (pair p in ratings)
                        {
                            if (p.getFirst() != i1)
                                recs[p.getFirst()]++;
                        }
                    }
                }
                foreach (KeyValuePair<double, List<pair>> item in r.itemKey)
                {
                    if (item.Key != i1)
                        recs[item.Key] = recs[item.Key] / i1Count;
                }
                List<pair> CP = new List<pair>();
                List<pair> ans = new List<pair>();
                foreach (KeyValuePair<double, double> rec in recs)
                {
                    pair p = new pair(rec.Key, rec.Value);
                    CP.Add(p);
                }
                IEnumerable<pair> sortedItems = CP.OrderByDescending(key => key.getSecond());
                for (int i = 0; i < CP.Count; i++)
                {
                    ans.Add(sortedItems.ElementAt(i));
                }
                itemCP.Add(i1, ans);
                return ans;
            }
        }
    }
}
